home *** CD-ROM | disk | FTP | other *** search
/ Magnum One / Magnum One (Mid-American Digital) (Disc Manufacturing).iso / d18 / shrink12.arc / SHRINK.PAS < prev    next >
Pascal/Delphi Source File  |  1989-03-10  |  49KB  |  1,118 lines

  1. Program Shrinker;
  2.  
  3. {$M 10240, 0, 0}
  4. {$F+}
  5.  
  6. { Shrink.Pas version 1.2  (C) Copyright 1989 by R. P. Byrne                   }
  7. {                                                                             }
  8. {   Compress a set of input files into a Zip file using Lempel-Ziv-Welch      }
  9. {   (LZW) compression techniques (the "shrink" method).                       }
  10.  
  11. Uses  Dos,
  12.       Crt,
  13.       MemAlloc,
  14.       StrProcs;
  15.  
  16. Const
  17.    CopyRight = 'Shrink (C) Copyright 1989 by R. P. Byrne';
  18.    Version   = 'Version 1.2 - Compiled on March 11, 1989';
  19.  
  20. Const
  21.  
  22.    BUFSIZE     =  10240;   { Use 10K file buffers                             }
  23.    MINBITS     =      9;   { Starting code size of 9 bits                     }
  24.    MAXBITS     =     13;   { Maximum code size of 13 bits                     }
  25.    TABLESIZE   =   8191;   { We'll need 4K entries in table                   }
  26.    SPECIAL     =    256;   { Special function code                            }
  27.    INCSIZE     =      1;   { Code indicating a jump in code size              }
  28.    CLEARCODE   =      2;   { Code indicating code table has been cleared      }
  29.    FIRSTENTRY  =    257;   { First available table entry                      }
  30.    UNUSED      =     -1;   { Prefix indicating an unused code table entry     }
  31.  
  32.    STDATTR     =    $23;   { Standard file attribute for DOS Find First/Next  }
  33.  
  34. Const
  35.    LOCAL_FILE_HEADER_SIGNATURE = $04034B50;
  36.  
  37. Type
  38.    Local_File_Header_Type = Record
  39.                                Signature              :  LongInt;
  40.                                Extract_Version_Reqd   :  Word;
  41.                                Bit_Flag               :  Word;
  42.                                Compress_Method        :  Word;
  43.                                Last_Mod_Time          :  Word;
  44.                                Last_Mod_Date          :  Word;
  45.                                Crc32                  :  LongInt;
  46.                                Compressed_Size        :  LongInt;
  47.                                Uncompressed_Size      :  LongInt;
  48.                                Filename_Length        :  Word;
  49.                                Extra_Field_Length     :  Word;
  50.                             end;
  51.  
  52. { Define the Central Directory record types }
  53.  
  54. Const
  55.    CENTRAL_FILE_HEADER_SIGNATURE = $02014B50;
  56.  
  57. Type
  58.    Central_File_Header_Type = Record
  59.                                  Signature            :  LongInt;
  60.                                  MadeBy_Version       :  Word;
  61.                                  Extract_Version_Reqd :  Word;
  62.                                  Bit_Flag             :  Word;
  63.                                  Compress_Method      :  Word;
  64.                                  Last_Mod_Time        :  Word;
  65.                                  Last_Mod_Date        :  Word;
  66.                                  Crc32                :  LongInt;
  67.                                  Compressed_Size      :  LongInt;
  68.                                  Uncompressed_Size    :  LongInt;
  69.                                  Filename_Length      :  Word;
  70.                                  Extra_Field_Length   :  Word;
  71.                                  File_Comment_Length  :  Word;
  72.                                  Starting_Disk_Num    :  Word;
  73.                                  Internal_Attributes  :  Word;
  74.                                  External_Attributes  :  LongInt;
  75.                                  Local_Header_Offset  :  LongInt;
  76.                               End;
  77.  
  78. Const
  79.    END_OF_CENTRAL_DIR_SIGNATURE = $06054B50;
  80.  
  81. Type
  82.    End_of_Central_Dir_Type =  Record
  83.                                  Signature               :  LongInt;
  84.                                  Disk_Number             :  Word;
  85.                                  Central_Dir_Start_Disk  :  Word;
  86.                                  Entries_This_Disk       :  Word;
  87.                                  Total_Entries           :  Word;
  88.                                  Central_Dir_Size        :  LongInt;
  89.                                  Start_Disk_Offset       :  LongInt;
  90.                                  ZipFile_Comment_Length  :  Word;
  91.                               end;
  92.  
  93. Const
  94.    Crc_32_Tab : Array[0..255] of LongInt = (
  95. $00000000, $77073096, $ee0e612c, $990951ba, $076dc419, $706af48f, $e963a535, $9e6495a3,
  96. $0edb8832, $79dcb8a4, $e0d5e91e, $97d2d988, $09b64c2b, $7eb17cbd, $e7b82d07, $90bf1d91,
  97. $1db71064, $6ab020f2, $f3b97148, $84be41de, $1adad47d, $6ddde4eb, $f4d4b551, $83d385c7,
  98. $136c9856, $646ba8c0, $fd62f97a, $8a65c9ec, $14015c4f, $63066cd9, $fa0f3d63, $8d080df5,
  99. $3b6e20c8, $4c69105e, $d56041e4, $a2677172, $3c03e4d1, $4b04d447, $d20d85fd, $a50ab56b,
  100. $35b5a8fa, $42b2986c, $dbbbc9d6, $acbcf940, $32d86ce3, $45df5c75, $dcd60dcf, $abd13d59,
  101. $26d930ac, $51de003a, $c8d75180, $bfd06116, $21b4f4b5, $56b3c423, $cfba9599, $b8bda50f,
  102. $2802b89e, $5f058808, $c60cd9b2, $b10be924, $2f6f7c87, $58684c11, $c1611dab, $b6662d3d,
  103. $76dc4190, $01db7106, $98d220bc, $efd5102a, $71b18589, $06b6b51f, $9fbfe4a5, $e8b8d433,
  104. $7807c9a2, $0f00f934, $9609a88e, $e10e9818, $7f6a0dbb, $086d3d2d, $91646c97, $e6635c01,
  105. $6b6b51f4, $1c6c6162, $856530d8, $f262004e, $6c0695ed, $1b01a57b, $8208f4c1, $f50fc457,
  106. $65b0d9c6, $12b7e950, $8bbeb8ea, $fcb9887c, $62dd1ddf, $15da2d49, $8cd37cf3, $fbd44c65,
  107. $4db26158, $3ab551ce, $a3bc0074, $d4bb30e2, $4adfa541, $3dd895d7, $a4d1c46d, $d3d6f4fb,
  108. $4369e96a, $346ed9fc, $ad678846, $da60b8d0, $44042d73, $33031de5, $aa0a4c5f, $dd0d7cc9,
  109. $5005713c, $270241aa, $be0b1010, $c90c2086, $5768b525, $206f85b3, $b966d409, $ce61e49f,
  110. $5edef90e, $29d9c998, $b0d09822, $c7d7a8b4, $59b33d17, $2eb40d81, $b7bd5c3b, $c0ba6cad,
  111. $edb88320, $9abfb3b6, $03b6e20c, $74b1d29a, $ead54739, $9dd277af, $04db2615, $73dc1683,
  112. $e3630b12, $94643b84, $0d6d6a3e, $7a6a5aa8, $e40ecf0b, $9309ff9d, $0a00ae27, $7d079eb1,
  113. $f00f9344, $8708a3d2, $1e01f268, $6906c2fe, $f762575d, $806567cb, $196c3671, $6e6b06e7,
  114. $fed41b76, $89d32be0, $10da7a5a, $67dd4acc, $f9b9df6f, $8ebeeff9, $17b7be43, $60b08ed5,
  115. $d6d6a3e8, $a1d1937e, $38d8c2c4, $4fdff252, $d1bb67f1, $a6bc5767, $3fb506dd, $48b2364b,
  116. $d80d2bda, $af0a1b4c, $36034af6, $41047a60, $df60efc3, $a867df55, $316e8eef, $4669be79,
  117. $cb61b38c, $bc66831a, $256fd2a0, $5268e236, $cc0c7795, $bb0b4703, $220216b9, $5505262f,
  118. $c5ba3bbe, $b2bd0b28, $2bb45a92, $5cb36a04, $c2d7ffa7, $b5d0cf31, $2cd99e8b, $5bdeae1d,
  119. $9b64c2b0, $ec63f226, $756aa39c, $026d930a, $9c0906a9, $eb0e363f, $72076785, $05005713,
  120. $95bf4a82, $e2b87a14, $7bb12bae, $0cb61b38, $92d28e9b, $e5d5be0d, $7cdcefb7, $0bdbdf21,
  121. $86d3d2d4, $f1d4e242, $68ddb3f8, $1fda836e, $81be16cd, $f6b9265b, $6fb077e1, $18b74777,
  122. $88085ae6, $ff0f6a70, $66063bca, $11010b5c, $8f659eff, $f862ae69, $616bffd3, $166ccf45,
  123. $a00ae278, $d70dd2ee, $4e048354, $3903b3c2, $a7672661, $d06016f7, $4969474d, $3e6e77db,
  124. $aed16a4a, $d9d65adc, $40df0b66, $37d83bf0, $a9bcae53, $debb9ec5, $47b2cf7f, $30b5ffe9,
  125. $bdbdf21c, $cabac28a, $53b39330, $24b4a3a6, $bad03605, $cdd70693, $54de5729, $23d967bf,
  126. $b3667a2e, $c4614ab8, $5d681b02, $2a6f2b94, $b40bbe37, $c30c8ea1, $5a05df1b, $2d02ef8d
  127. );
  128.  
  129. Type
  130.  
  131.    { Define data types needed to implement a code table for LZW compression   }
  132.    CodeRec     =  Record                { Code Table record format...         }
  133.                      Child   : Integer; { Addr of 1st suffix for this prefix  }
  134.                      Sibling : Integer; { Addr of next suffix in chain        }
  135.                      Suffix  : Byte;    { Suffix character                    }
  136.                   end {CodeRec};
  137.    CodeArray   =  Array[0..TABLESIZE] of CodeRec; { Define the code table     }
  138.    TablePtr    =  ^CodeArray;                     { Allocate dynamically      }
  139.  
  140.    { Define data types needed to implement a free node list                   }
  141.    FreeListPtr    =  ^FreeListArray;
  142.    FreeListArray  =  Array[FIRSTENTRY..TABLESIZE] of Word;
  143.  
  144.    { Define data types needed to implement input and output file buffers      }
  145.    BufArray    =  Array[1..BUFSIZE] of byte;
  146.    BufPtr      =  ^BufArray;
  147.  
  148.    { Define the structure of a DOS Disk Transfer Area (DTA)                   }
  149.    DTARec      =  Record         
  150.                      Filler   :  Array[1..21] of Byte;
  151.                      Attr     :  Byte;
  152.                      Time     :  Word;
  153.                      Date     :  Word;
  154.                      Size     :  LongInt;
  155.                      Name     :  String[12];
  156.                   end {DtaRec};
  157.  
  158.    { Define data types needed to implement a sorted singly linked list to     }
  159.    { hold the names of all files to be compressed                             }
  160.    NameStr      = String[12];
  161.    PathStr      = String[64];
  162.    NodePtr      = ^NameList;
  163.    NameList     = Record                  { Linked list node structure...     }
  164.                      Path : PathStr;      { Path of input file                }
  165.                      Name : NameStr;      { Name of input file                }
  166.                      Size : LongInt;      { Size in bytes of input file       }
  167.                      Date : Word;         { Date stamp of input file          }
  168.                      Time : Word;         { Time stamp of input file          }
  169.                      Next : NodePtr;      { Next node in linked list          }
  170.                   end {NameList};
  171.  
  172. Var
  173.    InFileSpecs :  Array[1..20] of String;    { Input file specifications      }
  174.    MaxSpecs    :  Word;          { Total number of filespecs to be Zipped     }
  175.    OutFileName :  String;        { Name of resulting Zip file                 }
  176.  
  177.    InFile,                       { I/O file variables                         }
  178.    OutFile     :  File;
  179.  
  180.    InBuf,                        { I/O buffers                                }
  181.    OutBuf      :  BufPtr;
  182.    InBufIdx,                     { Points to next char in buffer to be read   }
  183.    OutBufIdx   :  Word;          { Points to next free space in output buffer }
  184.    MaxInBufIdx :  Word;          { Count of valid chars in input buffer       }
  185.  
  186.    InputEof    :  Boolean;       { End of file indicator                      }
  187.  
  188.    Crc32Val    :  LongInt;       { CRC calculation variable                   }
  189.    CodeTable   :  TablePtr;      { Points to code table for LZW compression   }
  190.  
  191.    FreeList    :  FreeListPtr;   { Table of free code table entries           }
  192.    NextFree    :  Word;          { Index into free list table                 }
  193.  
  194.    ClearList   :  Array[0..1023] of Byte;  { Bit mapped structure used in     }
  195.                                            {    during adaptive resets        }
  196.    CodeSize    :  Byte;     { Size of codes (in bits) currently being written }
  197.    MaxCode     :  Word;   { Largest code that can be written in CodeSize bits }
  198.  
  199.    LocalHdr    :  Local_File_Header_Type;
  200.    LocalHdrOfs :  LongInt;  { Offset within output file of the local header   }
  201.    CentralHdr  :  Central_File_Header_Type;
  202.    EndHdr      :  End_of_Central_Dir_Type;
  203.  
  204.    FirstCh     :  Boolean;  { Flag indicating the START of a shrink operation }
  205.    TableFull   :  Boolean;  { Flag indicating a full symbol table             }
  206.  
  207.    SaveByte    :  Byte;     { Output code buffer                              }
  208.    BitsUsed    :  Byte;     { Index into output code buffer                   }
  209.  
  210.    BytesIn     :  LongInt;  { Count of input file bytes processed             }
  211.    BytesOut    :  LongInt;  { Count of output bytes                           }
  212.  
  213.    ListHead    :  NodePtr;  { Pointer to head of linked list                  }
  214.  
  215.    TenPercent  :  LongInt;
  216.  
  217. { --------------------------------------------------------------------------- }
  218. { Houskeeping stuff (error routines and initialization of program variables)  }
  219. { --------------------------------------------------------------------------- }
  220.  
  221. Procedure Syntax;
  222. Begin
  223.    Writeln('Shrink.Exe');
  224.    Writeln('   Usage:   Shrink zipfilename [filespec [...]]');
  225.    Writeln;
  226.    Writeln('   A filespec is defined as [d:][\path\]name');
  227.    Writeln('   where ''name'' may contain DOS wildcard characters.');
  228.    Writeln;
  229.    Writeln('   Multiple filespecs may be entered up to a maximum of 20.');
  230.    Writeln;
  231.    Writeln('   If no filespecs are entered, *.* is assumed.');
  232.    Writeln;
  233.    Halt(255);
  234. end {Syntax};
  235.  
  236. { --------------------------------------------------------------------------- }
  237.  
  238. Procedure Fatal(Msg : String);
  239. Begin
  240.    Writeln;
  241.    Writeln;
  242.    Writeln('Shrink.Exe');
  243.    Writeln('   Error: ', Msg);
  244.    Writeln('   Program halted');
  245.    Writeln;
  246.    Writeln;
  247.    Halt(128);
  248. end {Fatal};
  249.  
  250. { --------------------------------------------------------------------------- }
  251.  
  252. Procedure AddToList(PathSpec : PathStr; DTA : DTARec);
  253. { Add an entry to a linked list of filenames to be crunched.  Maintain        }
  254. { sorted order (standard ASCII collating sequence) by filename                }
  255. Var
  256.    MemError : Word;
  257.    NewNode  : NodePtr;
  258.    Done     : Boolean;
  259.    ListNode : NodePtr;
  260. Begin
  261.    { Allocate a new node                                                      }
  262.    MemError := Malloc(NewNode, SizeOf(NewNode^));
  263.    If MemError <> 0 then
  264.       Fatal('Not enough memory to process all filenames!');
  265.  
  266.    { Populate the fields of the new node                                      }
  267.    NewNode^.Path := PathSpec;
  268.    NewNode^.Name := DTA.Name;
  269.    NewNode^.Size := DTA.Size;
  270.    NewNode^.Date := DTA.Date;
  271.    NewNode^.Time := DTA.Time;
  272.    NewNode^.Next := NIL;
  273.  
  274.    { Find the proper location in the list at which to insert the new node     }
  275.    If ListHead = NIL then
  276.       ListHead := NewNode
  277.    else
  278.       If DTA.Name < ListHead^.Name then begin
  279.          NewNode^.Next := ListHead;
  280.          ListHead      := NewNode;
  281.       end {then}
  282.       else begin
  283.          Done     := FALSE;
  284.          ListNode := ListHead;
  285.          While NOT Done do begin
  286.             If ListNode^.Name = DTA.Name then begin
  287.                ListNode^.Path := PathSpec;
  288.                MemError := Dalloc(NewNode);
  289.                Done := TRUE;
  290.             end {then}
  291.             else
  292.                If ListNode^.Next = NIL then begin
  293.                   ListNode^.Next := NewNode;
  294.                   Done := TRUE;
  295.                end {then}
  296.                else
  297.                   If ListNode^.Next^.Name > DTA.Name then begin
  298.                      NewNode^.Next  := ListNode^.Next;
  299.                      ListNode^.Next := NewNode;
  300.                      Done := TRUE;
  301.                   end {then}
  302.                   else
  303.                      ListNode := ListNode^.Next;
  304.          end {while};
  305.       end {if};
  306. end {AddToList};
  307.  
  308. { --------------------------------------------------------------------------- }
  309.  
  310. Procedure GetNames;
  311. { Expand input file specifications.  Store the name of each file to be        }
  312. { compressed in a sorted, singly linked list                                  }
  313. Var
  314.    DosDTA   : DTARec;
  315.    I        : Word;
  316.    InPath   : String;
  317. Begin
  318.    ListHead := NIL;
  319.    For I := 1 to MaxSpecs do begin   { Loop through all input file specs      }
  320.       InPath := Upper(PathOnly(InFileSpecs[I]));
  321.       FindFirst(InFileSpecs[I], STDATTR, SearchRec(DosDTA));
  322.       While DosError = 0 do begin    { Loop through all matching files        }
  323.          If (NOT SameFile(InPath + DosDTA.Name, OutFileName)) then
  324.             AddToList(InPath, DosDTA);
  325.          FindNext(SearchRec(DosDTA));
  326.       end {while};
  327.    end {for};
  328. end {GetNames};
  329.  
  330. { --------------------------------------------------------------------------- }
  331.  
  332. Function ParamCheck : Boolean;
  333. { Verify all command line parameters                                          }
  334. Var
  335.    SearchBuf : SearchRec;
  336.    OutPath   : String;
  337.    Ch        : Char;
  338.    I         : Word;
  339. Begin
  340.  
  341.    If ParamCount < 1 then Syntax;
  342.    If ParamCount > 21 then begin
  343.       Writeln('Too many command line parameters entered!');
  344.       Syntax;
  345.    end {if};
  346.  
  347.    OutFileName := Upper(ParamStr(1));
  348.    If Pos('.', OutFileName) = 0 then
  349.       OutFileName := Concat(OutFileName, '.ZIP');
  350.  
  351.    FindFirst(OutFileName, STDATTR, SearchBuf);
  352.    If DosError = 0 then begin
  353.       Write(OutFileName, ' already exists!  Overwrite it (Y/N, Enter=N)? ');
  354.       Ch := ReadKey;
  355.       Writeln(Ch);
  356.       Writeln;
  357.       If UpCase(Ch) <> 'Y' then begin
  358.          Writeln;
  359.          Writeln('Program aborted!');
  360.          Halt;
  361.       end {if};
  362.    end {if};
  363.  
  364.    If ParamCount = 1 then begin
  365.       InFileSpecs[1] := '*.*';
  366.       MaxSpecs := 1;
  367.    end {then}
  368.    else
  369.       For I := 2 to ParamCount do begin
  370.          InFilespecs[Pred(I)] := ParamStr(I);
  371.          MaxSpecs := Pred(I);
  372.       end {for};
  373.  
  374.    GetNames;
  375.  
  376. End {ParamCheck};
  377.  
  378. { --------------------------------------------------------------------------- }
  379. { Running 32 Bit CRC update function                                          }
  380. { --------------------------------------------------------------------------- }
  381.  
  382. Function UpdC32(Octet: Byte; Crc: LongInt) : LongInt;
  383. Var
  384.    L : LongInt;
  385.    W : Array[1..4] of Byte Absolute L;
  386. Begin
  387.  
  388.    UpdC32 := Crc_32_Tab[Byte(Crc XOR LongInt(Octet))] XOR ((Crc SHR 8) AND $00FFFFFF);
  389.  
  390. end {UpdC32};
  391.  
  392. { --------------------------------------------------------------------------- }
  393. { I/O Support routines                                                        }
  394. { --------------------------------------------------------------------------- }
  395.  
  396. Procedure GetBuffers;
  397. { Allocate Input and Output buffers                                           }
  398. Var
  399.    MemError : Word;
  400. Begin
  401.    MemError := Malloc(InBuf, Sizeof(InBuf^));
  402.    If MemError <> 0 then
  403.       Fatal(Concat('Cannot allocate Input buffer',
  404.                    #13#10,
  405.                    '           DOS Return Code on allocation request was ',
  406.                    IntStr(MemError, 0)));
  407.  
  408.    MemError := Malloc(OutBuf, Sizeof(OutBuf^));
  409.    If MemError <> 0 then
  410.       Fatal(Concat('Cannot allocate Output buffer',
  411.                    #13#10,
  412.                    '           DOS Return Code on allocation request was ',
  413.                    IntStr(MemError, 0)));
  414. End {GetBuffers};
  415.  
  416. { --------------------------------------------------------------------------- }
  417.  
  418. Procedure DropBuffers;
  419. { Deallocate input and output buffers                                         }
  420. Var
  421.    MemError : Word;
  422. Begin
  423.    MemError := Dalloc(InBuf);
  424.    MemError := Dalloc(OutBuf);
  425. end {DropBuffers};
  426.  
  427. { --------------------------------------------------------------------------- }
  428.  
  429. Procedure OpenOutput;
  430. Var
  431.    RC : Integer;
  432. Begin
  433.    Assign(OutFile, OutFileName);
  434.    FileMode := 66;
  435.    {$I-} ReWrite(OutFile, 1); {$I+}
  436.    RC := IOResult;
  437.    If RC <> 0 then
  438.       Fatal(Concat('Cannot open output file',
  439.                    #13#10,
  440.                    '           Return Code was ',
  441.                    IntStr(RC, 0)));
  442. End {OpenOutput};
  443.  
  444. { --------------------------------------------------------------------------- }
  445.  
  446. Function OpenInput(InFileName : String) : Boolean;
  447. Var
  448.    RC : Integer;
  449. Begin
  450.    Assign(InFile, InFileName);
  451.    FileMode := 64;
  452.    {$I-} Reset(InFile, 1); {$I+}
  453.    OpenInput := (IOResult = 0);
  454. End {OpenInput};
  455.  
  456. { --------------------------------------------------------------------------- }
  457.  
  458. Procedure CloseOutput;
  459. Var
  460.    RC : Integer;
  461. Begin
  462.    {$I-} Close(OutFile) {$I+};
  463.    RC := IOResult;
  464. end {CloseOutput};
  465.  
  466. { --------------------------------------------------------------------------- }
  467.  
  468. Procedure CloseInput;
  469. Var
  470.    RC : Integer;
  471. Begin
  472.    {$I-} Close(InFile)  {$I+};
  473.    RC := IOResult;
  474. end {CloseInput};
  475.  
  476. { --------------------------------------------------------------------------- }
  477.  
  478. Procedure Read_Block;
  479. { Read a "block" of data into our our input buffer                            }
  480. Begin
  481.    BlockRead(InFile, InBuf^[1], SizeOf(InBuf^), MaxInBufIdx);
  482.    If MaxInBufIdx = 0 then
  483.       InputEof := TRUE
  484.    else
  485.       InputEOF := FALSE;
  486.    InBufIdx := 1;
  487. end {Read_Block};
  488.  
  489. { --------------------------------------------------------------------------- }
  490.  
  491. Procedure Write_Block;
  492. { Write a block of data from the output buffer to our output file             }
  493. Begin
  494.    BlockWrite(OutFile, OutBuf^[1], Pred(OutBufIdx));
  495.    OutBufIdx := 1;
  496. end {Write_Block};
  497.  
  498. { --------------------------------------------------------------------------- }
  499.  
  500. Procedure PutChar(B : Byte);
  501. { Put one character into our output buffer                                    }
  502. Begin
  503.    OutBuf^[OutBufIdx] := B;
  504.    Inc(OutBufIdx);
  505.    If OutBufIdx > SizeOf(OutBuf^) then
  506.       Write_Block;
  507.    Inc(BytesOut);
  508. end {PutChar};
  509.  
  510. { --------------------------------------------------------------------------- }
  511.  
  512. Procedure FlushOutput;
  513. { Write any data sitting in our output buffer to the output file              }
  514. Begin
  515.    If OutBufIdx > 1 then
  516.       Write_Block;
  517. End {FlushOutput};
  518.  
  519. { --------------------------------------------------------------------------- }
  520.  
  521. Procedure PutCode(Code : Integer);
  522. { Assemble coded bytes for output                                             }
  523. Var
  524.    PutCharAddr : Pointer;
  525. Begin
  526.    PutCharAddr := @PutChar;
  527.  
  528.    Inline(
  529.                             {;  Register useage:}
  530.                             {;}
  531.                             {;  AX - holds Code}
  532.                             {;  BX - BH is a work register, BL holds SaveByte}
  533.                             {;  CX - holds our loop counter CodeSize}
  534.                             {;  DX - holds BitsUsed}
  535.                             {;}
  536.      $8B/$46/<Code/         {                mov         ax,[bp+<Code]}
  537.      $31/$DB/               {                xor         bx,bx}
  538.      $89/$D9/               {                mov         cx,bx}
  539.      $89/$DA/               {                mov         dx,bx}
  540.      $8A/$1E/>SaveByte/     {                mov         bl,[>SaveByte]}
  541.      $8A/$0E/>CodeSize/     {                mov         cl,[>CodeSize]}
  542.      $8A/$16/>BitsUsed/     {                mov         dl,[>BitsUsed]}
  543.      $3D/$FF/$FF/           {                cmp         ax,-1               ;Any work to do?}
  544.      $75/$0D/               {                jnz         Repeat              ;Yup, go do it}
  545.      $80/$FA/$00/           {                cmp         dl,0                ;Any leftovers?}
  546.      $74/$3A/               {                jz          AllDone             ;Nope, we're done}
  547.      $53/                   {                push        bx                  ;Yup...push leftovers}
  548.      $0E/                   {                push        cs}
  549.      $FF/$96/>PutCharAddr/  {                call        [bp+>PutCharAddr]   ;   and send to output}
  550.      $EB/$32/               {                jmp short   AllDone}
  551.                             {;}
  552.      $30/$FF/               {Repeat:         xor         bh,bh               ;Zero out BH}
  553.      $D1/$D8/               {                rcr         ax,1                ;Get low order bit into CY flag}
  554.      $73/$02/               {                jnc         SkipBit             ;Was the bit set?}
  555.      $FE/$C7/               {                inc         bh                  ;Yes, xfer to BH}
  556.      $87/$D1/               {SkipBit:        xchg        cx,dx               ;Swap CX & DX}
  557.      $D2/$E7/               {                shl         bh,cl               ;Shift bit over}
  558.      $87/$D1/               {                xchg        cx,dx               ;Put CX & DX back where they were}
  559.      $42/                   {                inc         dx                  ;Bump count of bit positions used}
  560.      $08/$FB/               {                or          bl,bh               ;Transfer bit to output byte (SaveByte)}
  561.      $83/$FA/$08/           {                cmp         dx,8                ;Full byte yet?}
  562.      $72/$12/               {                jb          GetNext             ;Nope, go get more code bits}
  563.      $50/                   {                push        ax                  ;Yup, save regs in preparation}
  564.      $53/                   {                push        bx                  ;    for call to output routine}
  565.      $51/                   {                push        cx}
  566.      $52/                   {                push        dx}
  567.      $53/                   {                push        bx                  ;Push byte to output onto stack}
  568.      $0E/                   {                push        cs}
  569.      $FF/$96/>PutCharAddr/  {                call        [bp+>PutCharAddr]   ;   and call the output routine}
  570.      $5A/                   {                pop         dx}
  571.      $59/                   {                pop         cx}
  572.      $5B/                   {                pop         bx}
  573.      $58/                   {                pop         ax}
  574.      $31/$DB/               {                xor         bx,bx               ;Prepare SaveByte for next byte}
  575.      $89/$DA/               {                mov         dx,bx               ;Set BitsUsed to zero}
  576.      $E2/$D6/               {GetNext:        loop        Repeat              ;Repeat for all code bits}
  577.                             {;}
  578.      $88/$1E/>SaveByte/     {                mov         [>SaveByte],bl      ;Put SaveByte and BitsUsed}
  579.      $88/$16/>BitsUsed);    {                mov         [>BitsUsed],dl      ;   back in memory}
  580.                             {;}
  581.                             {AllDone:}
  582.    
  583. end {Putcode};
  584.  
  585. { --------------------------------------------------------------------------- }
  586. { The following routines are used to allocate, initialize, and de-allocate    }
  587. { various dynamic memory structures used by the LZW compression algorithm     }
  588. { --------------------------------------------------------------------------- }
  589.  
  590. Procedure Build_Data_Structures;
  591. Var
  592.    Code  :  Word;
  593. Begin
  594.    Code  := Malloc(CodeTable, SizeOf(CodeTable^)) OR
  595.             Malloc(FreeList,  SizeOf(FreeList^ ));
  596.    If Code <> 0 then
  597.       Fatal('Not enough memory to allocate LZW data structures!');
  598. end {Build_Data_Structures};
  599.  
  600. { --------------------------------------------------------------------------- }
  601.  
  602. Procedure Destroy_Data_Structures;
  603. Var
  604.    Code  :  Word;
  605. Begin
  606.    Code := Dalloc(CodeTable);
  607.    Code := Dalloc(FreeList);
  608. end {Destroy_Data_Structures};
  609.  
  610. { --------------------------------------------------------------------------- }
  611.  
  612. Procedure Initialize_Data_Structures;
  613. Var
  614.    I  :  Word;
  615. Begin
  616.    For I := 0 to TableSize do begin
  617.       With CodeTable^[I] do begin
  618.          Child     := -1;
  619.          Sibling   := -1;
  620.          If I <= 255 then
  621.             Suffix := I;
  622.       end {with};
  623.       If I >= 257 then
  624.          FreeList^[I] := I;
  625.    end {for};
  626.  
  627.    NextFree  := FIRSTENTRY;
  628.    TableFull := FALSE;
  629.  
  630. end {Initialize_Data_Structures};
  631.  
  632. { --------------------------------------------------------------------------- }
  633. { The following routines handle manipulation of the LZW Code Table            }
  634. { --------------------------------------------------------------------------- }
  635.  
  636. Procedure Prune(Parent : Word);
  637. { Prune leaves from a subtree - Note: this is a recursive procedure }
  638. Var
  639.    CurrChild   : Integer;
  640.    NextSibling : Integer;
  641. Begin
  642.    CurrChild := CodeTable^[Parent].Child;
  643.    { Find first Child that has descendants .. clear any that don't }
  644.    While (CurrChild <> -1) AND (CodeTable^[CurrChild].Child = -1) do begin
  645.       CodeTable^[Parent].Child := CodeTable^[CurrChild].Sibling;
  646.       CodeTable^[CurrChild].Sibling := -1;
  647.       { Turn on ClearList bit to indicate a cleared entry }
  648.       ClearList[CurrChild DIV 8] := (ClearList[CurrChild DIV 8] OR (1 SHL (CurrChild MOD 8)));
  649.       CurrChild := CodeTable^[Parent].Child;
  650.    end {while};
  651.  
  652.    If CurrChild <> -1 then begin   { If there are any children left ...}
  653.       Prune(CurrChild);
  654.       NextSibling := CodeTable^[CurrChild].Sibling;
  655.       While NextSibling <> -1 do begin
  656.          If CodeTable^[NextSibling].Child = -1 then begin
  657.             CodeTable^[CurrChild].Sibling := CodeTable^[NextSibling].Sibling;
  658.             CodeTable^[NextSibling].Sibling := -1;
  659.             { Turn on ClearList bit to indicate a cleared entry }
  660.             ClearList[NextSibling DIV 8] := (ClearList[NextSibling DIV 8] OR (1 SHL (NextSibling MOD 8)));
  661.             NextSibling := CodeTable^[CurrChild].Sibling;
  662.          end {then}
  663.          else begin
  664.             CurrChild := NextSibling;
  665.             Prune(CurrChild);
  666.             NextSibling := CodeTable^[CurrChild].Sibling;
  667.          end {if};
  668.       end {while};
  669.    end {if};
  670.  
  671. end {Prune};
  672.  
  673. { --------------------------------------------------------------------------- }
  674.  
  675. Procedure Clear_Table;
  676. Var
  677.    Node : Word;
  678. Begin
  679.    FillChar(ClearList, SizeOf(ClearList), $00);
  680.    { Remove all leaf nodes by recursively pruning subtrees}
  681.    For Node := 0 to 255 do
  682.       Prune(Node);
  683.    { Next, re-initialize our list of free table entries }
  684.    NextFree := Succ(TABLESIZE);
  685.    For Node := TABLESIZE downto FIRSTENTRY do begin
  686.       If (ClearList[Node DIV 8] AND (1 SHL (Node MOD 8))) <> 0 then begin
  687.          Dec(NextFree);
  688.          FreeList^[NextFree] := Node;
  689.       end {if};
  690.    end {for};
  691.    If NextFree <= TABLESIZE then
  692.       TableFull := FALSE;
  693. end {Clear_Table};
  694.  
  695. { --------------------------------------------------------------------------- }
  696.  
  697. Procedure Table_Add(Prefix : Word; Suffix : Byte);
  698. Var
  699.    FreeNode : Word;
  700. Begin
  701.    If NextFree <= TABLESIZE then begin
  702.       FreeNode := FreeList^[NextFree];
  703.       Inc(NextFree);
  704.       CodeTable^[FreeNode].Child := -1;
  705.       CodeTable^[FreeNode].Sibling := -1;
  706.       CodeTable^[FreeNode].Suffix := Suffix;
  707.       If CodeTable^[Prefix].Child  = -1 then
  708.          CodeTable^[Prefix].Child := FreeNode
  709.       else begin
  710.          Prefix := CodeTable^[Prefix].Child;
  711.          While CodeTable^[Prefix].Sibling <> -1 do
  712.             Prefix := CodeTable^[Prefix].Sibling;
  713.          CodeTable^[Prefix].Sibling := FreeNode;
  714.       end {if};
  715.    end {if};
  716.  
  717.    If NextFree > TABLESIZE then
  718.       TableFull := TRUE;
  719. end {Table_Add};
  720.  
  721. { --------------------------------------------------------------------------- }
  722.  
  723. Function Table_Lookup(    TargetPrefix : Integer;
  724.                           TargetSuffix : Byte;
  725.                       Var FoundAt      : Integer   ) : Boolean;
  726. { --------------------------------------------------------------------------- }
  727. { Search for a Prefix:Suffix pair in our Symbol table.  If found, return the  }
  728. { index value where found.  If not found, return FALSE and set the VAR parm   }
  729. { FoundAt to -1.                                                              }
  730. { --------------------------------------------------------------------------- }
  731. Begin
  732.    Inline(
  733.                             {;}
  734.                             {; Lookup an entry in the Hash Table.  If found, return TRUE and set the VAR}
  735.                             {; parameter FoundAt with the index of the entry at which the match was found.}
  736.                             {; If not found, return FALSE and plug a -1 into the FoundAt var.}
  737.                             {;}
  738.                             {;}
  739.                             {; Register usage:}
  740.                             {;   AX - varies                     BL - holds target suffix character}
  741.                             {;                                   BH - If search fails, determines how to}
  742.                             {;                                        add the new entry}
  743.                             {;   CX - not used                   DX - holds size of 1 table entry (5)}
  744.                             {;   DI - varies                     SI - holds offset of 1st table entry}
  745.                             {;   ES - seg addr of hash table     DS - program's data segment}
  746.                             {;}
  747.                             {;}
  748.      $8A/$5E/<TargetSuffix/ {            mov byte    bl,[bp+<TargetSuffix]   ;Target Suffix character}
  749.      $8B/$46/<TargetPrefix/ {            mov word    ax,[bp+<TargetPrefix]   ;Index into table}
  750.      $BA/$05/$00/           {            mov         dx,5                    ;5 byte table entries}
  751.      $F7/$E2/               {            mul         dx                      ;AX now an offset into table}
  752.      $C4/$3E/>CodeTable/    {            les         di,[>CodeTable]         ;Hash table address}
  753.      $89/$FE/               {            mov         si,di                   ;save offset in SI}
  754.      $01/$C7/               {            add         di,ax                   ;es:di points to table entry}
  755.                             {;}
  756.      $B7/$00/               {            mov         bh,0                    ;Chain empty flag (0=empty)}
  757.      $26/$83/$3D/$FF/       {        es: cmp word    [di],-1                 ;Anything on the chain?}
  758.      $74/$33/               {            jz          NotFound                ;Nope, search fails}
  759.      $B7/$01/               {            mov         bh,1                    ;Chain empty flag (1=not empty)}
  760.                             {;}
  761.      $26/$8B/$05/           {        es: mov word    ax,[di]                 ;Get index of 1st entry in chain}
  762.      $89/$46/<TargetPrefix/ {Loop:       mov word    [bp+<TargetPrefix],ax   ;Save index for later}
  763.      $BA/$05/$00/           {            mov         dx,5}
  764.      $F7/$E2/               {            mul         dx                      ;convert index to offset}
  765.      $89/$F7/               {            mov         di,si                   ;es:di points to start of table}
  766.      $01/$C7/               {            add         di,ax                   ;es:di points to table entry}
  767.                             {;}
  768.      $26/$3A/$5D/$04/       {        es: cmp byte    bl,[di+4]               ;match on suffix?}
  769.      $74/$0D/               {            jz          Found                   ;Yup, search succeeds}
  770.                             {;}
  771.      $26/$83/$7D/$02/$FF/   {        es: cmp word    [di+2],-1               ;any more entries in chain?}
  772.      $74/$15/               {            jz          NotFound                ;nope, search fails}
  773.                             {;}
  774.      $26/$8B/$45/$02/       {        es: mov word    ax,[di+2]               ;get index of next chain entry}
  775.      $EB/$E1/               {            jmp short   Loop                    ;   and keep searching}
  776.                             {;}
  777.      $C6/$46/$FF/$01/       {Found:      mov byte    [bp-1],1                ;return TRUE}
  778.      $C4/$7E/<FoundAt/      {            les         di,[bp+<FoundAt]        ;get address of Var parameter}
  779.      $8B/$46/<TargetPrefix/ {            mov word    ax,[bp+<TargetPrefix]   ;get index of entry where found}
  780.      $26/$89/$05/           {        es: mov         [di],ax                 ;and store it}
  781.      $EB/$0C/               {            jmp short   Done}
  782.                             {;}
  783.      $C6/$46/$FF/$00/       {NotFound:   mov byte    [bp-1],0                ;return FALSE}
  784.      $C4/$7E/<FoundAt/      {            les         di,[bp+<FoundAt]        ;get address of Var parameter}
  785.      $26/$C7/$05/$FF/$FF);  {        es: mov word    [di],-1                 ;and store a -1 in it}
  786.                             {;}
  787.                             {Done:}
  788.                             {;}
  789.    
  790. end {Table_Lookup};
  791.  
  792. { --------------------------------------------------------------------------- }
  793. { These routines build the Header structures for the ZIP file                 }
  794. { --------------------------------------------------------------------------- }
  795.  
  796. Procedure Begin_ZIP(ListPtr : NodePtr);
  797. { Write a dummy header to the zip.  Include as much info as is currently      }
  798. { known (we'll come back and fill in the rest later...)                       }
  799. Begin
  800.    LocalHdrOfs := FilePos(OutFile);       { Save file position for later use  }
  801.    With LocalHdr do begin
  802.       Signature := LOCAL_FILE_HEADER_SIGNATURE;
  803.       Extract_Version_Reqd := 10;
  804.       Bit_Flag := 0;
  805.       Compress_Method := 1;
  806.       Last_Mod_Time := ListPtr^.Time;
  807.       Last_Mod_Date := ListPtr^.Date;
  808.       Crc32 := 0;
  809.       Compressed_Size := 0;
  810.       Uncompressed_Size := ListPtr^.Size;
  811.       FileName_Length := Length(ListPtr^.Name);
  812.       Extra_Field_Length := 0;
  813.    end {with};
  814.    Move(LocalHdr, OutBuf^, SizeOf(LocalHdr)); { Put header into output buffer }
  815.    OutBufIdx := Succ(SizeOf(LocalHdr));   {...adjust buffer index accordingly }
  816.    Move(ListPtr^.Name[1], OutBuf^[OutBufIdx], Length(ListPtr^.Name));
  817.    Inc(OutBufIdx, Length(ListPtr^.Name));
  818.    FlushOutput;                           { Write it now                      }
  819. End {Begin_ZIP};
  820.  
  821. { --------------------------------------------------------------------------- }
  822.  
  823. Procedure Update_ZIP_Header(ListPtr : NodePtr);
  824. { Update the zip's local header with information that we now possess.  Check  }
  825. { to make sure that our shrinker actually produced a smaller file.  If not,   }
  826. { scrap the shrunk data, modify the local header accordingly, and just copy   }
  827. { the input file to the output file (compress method 0 - Storing).            }
  828. Var
  829.    EndPos : LongInt;
  830.    Redo   : Boolean;
  831. Begin
  832.    Redo := FALSE;                            { Set REDO flag to false         }
  833.    EndPos := FilePos(OutFile);               { Save current file position     }
  834.  
  835.    Seek(OutFile, LocalHdrOfs);               { Rewind back to file header     }
  836.  
  837.    With LocalHdr do begin
  838.                                              { Update compressed size field   }
  839.       Compressed_Size := EndPos - LocalHdrOfs - SizeOf(LocalHdr) - Filename_Length;
  840.       Crc32 := Crc32Val;                     { Update CRC value               }
  841.                                              { Have we compressed the file?   }
  842.       Redo := (Compressed_Size >= Uncompressed_Size);
  843.       If Redo then begin                     { No...                          }
  844.          Compress_Method := 0;                  { ...change stowage type      }
  845.          Compressed_Size := Uncompressed_Size;  { ...update compressed size   }
  846.       end {if};
  847.  
  848.    end {with};
  849.  
  850.    Move(LocalHdr, OutBuf^, SizeOf(LocalHdr)); { Put header into output buffer }
  851.    OutBufIdx := Succ(SizeOf(LocalHdr));   {...adjust buffer index accordingly }
  852.    Move(ListPtr^.Name[1], OutBuf^[OutBufIdx], Length(ListPtr^.Name));
  853.    Inc(OutBufIdx, Length(ListPtr^.Name));
  854.    FlushOutput;                           { Write it now                      }
  855.  
  856.    If Redo then begin
  857.       { If compression didn't make a smaller file, then ...                   }
  858.       Seek(InFile, 0);                       { Rewind the input file          }
  859.       InputEof := FALSE;                     { Reset EOF indicator            }
  860.       Read_Block;                            { Prime the input buffer         }
  861.       While NOT InputEof do begin            { Copy input to output           }
  862.          BlockWrite(OutFile, InBuf^, MaxInBufIdx);
  863.          Read_Block;
  864.       end {while};
  865.       Truncate(Outfile);                     { Truncate output file           }
  866.    end {then}
  867.    else begin
  868.       { Compression DID make a smaller file ...                               }
  869.       Seek(OutFile, FileSize(OutFile));   { Move output file pos back to eof  }
  870.    end {if};
  871. End {Update_ZIP_Header};
  872.  
  873. { --------------------------------------------------------------------------- }
  874.  
  875. Procedure Build_Central_Dir;
  876. { Revisit each local file header to build the Central Directory.  When done,  }
  877. { build the End of Central Directory record.                                  }
  878. Var
  879.    BytesRead : Word;
  880.    SavePos   : LongInt;
  881.    HdrPos    : LongInt;
  882.    CenDirPos : LongInt;
  883.    Entries   : Word;
  884.    FileName  : String;
  885. Begin
  886.    Entries := 0;
  887.    CenDirPos := FilePos(Outfile);
  888.    Seek(OutFile, 0);             { Rewind output file }
  889.    HdrPos := FilePos(OutFile);
  890.    BlockRead(OutFile, LocalHdr, SizeOf(LocalHdr), BytesRead);
  891.    Repeat
  892.       BlockRead(OutFile, FileName[1], LocalHdr.FileName_Length, BytesRead);
  893.       FileName[0] := Chr(LocalHdr.FileName_Length);
  894.       SavePos := FilePos(OutFile);
  895.  
  896.       With CentralHdr do begin
  897.          Signature := CENTRAL_FILE_HEADER_SIGNATURE;
  898.          MadeBy_Version := LocalHdr.Extract_Version_Reqd;
  899.          Move(LocalHdr.Extract_Version_Reqd, Extract_Version_Reqd, 26);
  900.          File_Comment_Length := 0;
  901.          Starting_Disk_Num := 0;
  902.          Internal_Attributes := 0;
  903.          External_Attributes := ARCHIVE;
  904.          Local_Header_Offset := HdrPos;
  905.          Seek(OutFile, FileSize(OutFile));
  906.          BlockWrite(Outfile, CentralHdr, SizeOf(CentralHdr));
  907.          BlockWrite(OutFile, FileName[1], Length(FileName));
  908.          Inc(Entries);
  909.       end {with};
  910.  
  911.       Seek(OutFile, SavePos + LocalHdr.Compressed_Size);
  912.       HdrPos := FilePos(OutFile);
  913.       BlockRead(OutFile, LocalHdr, SizeOf(LocalHdr), BytesRead);
  914.    Until LocalHdr.Signature = CENTRAL_FILE_HEADER_SIGNATURE;
  915.  
  916.    Seek(OutFile, FileSize(OutFile));
  917.  
  918.    With EndHdr do begin
  919.       Signature := END_OF_CENTRAL_DIR_SIGNATURE;
  920.       Disk_Number := 0;
  921.       Central_Dir_Start_Disk := 0;
  922.       Entries_This_Disk := Entries;
  923.       Total_Entries := Entries;
  924.       Central_Dir_Size := CenDirPos - FileSize(OutFile);
  925.       Start_Disk_Offset := CenDirPos;
  926.       ZipFile_Comment_Length := 0;
  927.       BlockWrite(Outfile, EndHdr, SizeOf(EndHdr));
  928.    end {with};
  929.  
  930. end {Build_Central_Dir};
  931.  
  932. { --------------------------------------------------------------------------- }
  933. { The actual Crunching algorithm                                              }
  934. { --------------------------------------------------------------------------- }
  935.  
  936. Procedure Shrink(Suffix : Integer);
  937. Const
  938.    LastCode    : Integer = 0;   { Typed constant, so value retained across calls }
  939. Var
  940.    WhereFound   : Integer;
  941.    CrunchRatio  : LongInt;
  942. Begin
  943.    If FirstCh then begin         { If just getting started ...                }
  944.       SaveByte := $00;           { Initialize our output code buffer          }
  945.       BitsUsed := 0;
  946.       CodeSize := MINBITS;       {     Initialize code size to minimum        }
  947.       MaxCode  := (1 SHL CodeSize) - 1;
  948.       LastCode := Suffix;        {     get first character from input,        }
  949.       FirstCh  := FALSE;         {     and reset the first char flag.         }
  950.    end {then}
  951.    else begin
  952.       If Suffix <> -1 then begin { If there's work to do ...                  }
  953.          If TableFull then begin
  954.             { Ok, lets clear the code table (adaptive reset)            }
  955.             Putcode(LastCode);
  956.             PutCode(SPECIAL);
  957.             Putcode(CLEARCODE);
  958.             Clear_Table;
  959.             Table_Add(LastCode, Suffix);
  960.             LastCode := Suffix;
  961.          end {then}
  962.          else begin
  963.             If Table_Lookup(LastCode, Suffix, WhereFound) then begin
  964.                { If LastCode:Suffix pair is found in the code table, then ...    }
  965.                { ... set LastCode to the entry where the pair is located         }
  966.                LastCode  := WhereFound;
  967.             end {then}
  968.             else begin
  969.                { Not in table                                                    }
  970.                PutCode(LastCode);            { Write current LastCode code       }
  971.                Table_Add(LastCode, Suffix);  { Attempt to add to code table      }
  972.                LastCode := Suffix;           { Reset LastCode code for new char  }
  973.                If (FreeList^[NextFree] > MaxCode) and (CodeSize < MaxBits) then begin
  974.                   { Time to increase the code size and change the max. code      }
  975.                   PutCode(SPECIAL);
  976.                   PutCode(INCSIZE);
  977.                   Inc(CodeSize);
  978.                   MaxCode := (1 SHL CodeSize) -1;
  979.                end {if};
  980.             end {if};
  981.          end {if};
  982.       end {then}
  983.       else begin                    { Nothing to crunch...must be EOF on input   }
  984.          PutCode(LastCode);         { Write last prefix code                     }
  985.          PutCode(-1);               { Tell putcode to flush remaining bits       }
  986.          FlushOutput;               { Flush our output buffer                    }
  987.       end {if};
  988.    end {if};
  989. end {Crunch};
  990.  
  991. { --------------------------------------------------------------------------- }
  992.  
  993. Procedure Process_Input(Source : String);
  994. Var
  995.    I       : Word;
  996.    PctDone : Integer;
  997. Begin
  998.    If Source = '' then
  999.       Shrink(-1)
  1000.    else
  1001.       For I := 1 to Length(Source) do begin
  1002.          Inc(BytesIn);
  1003.          If (Pred(BytesIn) MOD TenPercent) = 0 then begin
  1004.             PctDone := Round( 100 * ( BytesIn / FileSize(InFile)));
  1005.             GotoXY(WhereX - 4, WhereY);
  1006.             Write(PctDone:3, '%');
  1007.          end {if};
  1008.          CRC32Val := UpdC32(Ord(Source[I]), CRC32Val);
  1009.          Shrink(Ord(Source[I]));
  1010.       end {for};
  1011. end {Process_Input};
  1012.  
  1013. { --------------------------------------------------------------------------- }
  1014. { This routine handles processing for one input file                          }
  1015. { --------------------------------------------------------------------------- }
  1016.  
  1017. Procedure Process_One_File;
  1018. Var
  1019.    OneString : String;
  1020.    Remaining : Word;
  1021. Begin
  1022.  
  1023.    Read_Block;                { Prime the input buffer                        }
  1024.    FirstCh   := TRUE;         { 1st character flag for Crunch procedure       }
  1025.    Crc32Val  := $FFFFFFFF;
  1026.  
  1027.    TenPercent := FileSize(InFile) DIV 10;
  1028.  
  1029.    While NOT InputEof do begin
  1030.       Remaining := Succ(MaxInBufIdx - InBufIdx);
  1031.  
  1032.       If Remaining > 255 then
  1033.          Remaining := 255;
  1034.  
  1035.       If Remaining = 0 then
  1036.          Read_Block
  1037.       else begin
  1038.          Move(InBuf^[InBufIdx], OneString[1], Remaining);
  1039.          OneString[0] := Chr(Remaining);
  1040.          Inc(InBufIdx, Remaining);
  1041.          Process_Input(OneString);
  1042.       end {if};
  1043.  
  1044.    end {while};
  1045.  
  1046.    Crc32Val := NOT Crc32Val;
  1047.  
  1048.    Process_Input('');     { This forces EOF processing }
  1049.  
  1050. end {Process_One_File};
  1051.  
  1052. { --------------------------------------------------------------------------- }
  1053.  
  1054. Procedure Process_All_Files;
  1055. Var
  1056.    InPath   : String;
  1057.    ComprPct : Word;
  1058.    ListNode : NodePtr;
  1059. Begin
  1060.    If ListHead = NIL then begin
  1061.       Writeln;
  1062.       Writeln('There are no files to shrink!');
  1063.       Writeln;
  1064.       Halt;
  1065.    end {if};
  1066.  
  1067.    OpenOutput;
  1068.  
  1069.    ListNode := ListHead;
  1070.    While ListNode <> NIL do begin
  1071.       If OpenInput(Concat(ListNode^.Path, ListNode^.Name)) then begin
  1072.          Write('Processing ', ListNode^.Name, ' ');
  1073.          While WhereX < 28 do
  1074.             Write('.');
  1075.          Write('    ');
  1076.          BytesIn := 1; BytesOut := 1;
  1077.          TenPercent := FileSize(InFile) DIV 10;
  1078.          Initialize_Data_Structures;
  1079.          Begin_ZIP(ListNode);
  1080.          Process_One_File;
  1081.          Update_ZIP_Header(ListNode);
  1082.          CloseInput;
  1083.          If LocalHdr.Uncompressed_Size > 0 then
  1084.             ComprPct := Round((100.0 * (LocalHdr.Uncompressed_Size - LocalHdr.Compressed_Size)) / LocalHdr.Uncompressed_Size)
  1085.          else
  1086.             ComprPct := 0;
  1087.          GotoXY(WhereX - 4, WhereY);
  1088.          ClrEol;
  1089.          Writeln(' done (compression = ', ComprPct:2, '%)');
  1090.       end {then}
  1091.       else
  1092.          Writeln('Could not open ', ListNode^.Name, '.  Skipping this file ...');
  1093.       ListNode := ListNode^.Next;
  1094.    end {while};
  1095.    Build_Central_Dir;
  1096.    CloseOutput;
  1097. End {Process_All_Files};
  1098.  
  1099. { --------------------------------------------------------------------------- }
  1100. { Main Program (driver)                                                       }
  1101. { --------------------------------------------------------------------------- }
  1102.  
  1103. Begin
  1104.    Assign(Output, '');        { Reset output to DOS stdout device             }
  1105.    Rewrite(Output);
  1106.    Writeln;
  1107.    Writeln(Copyright);
  1108.    Writeln(Version);
  1109.    Writeln;
  1110.    If ParamCheck then begin
  1111.       GetBuffers;              { Allocate input and output buffers ...        }
  1112.       Build_Data_Structures;   { ... and other data structures required       }
  1113.       Process_All_Files;       { Crunch the file                              }
  1114.       DropBuffers;             { Be polite and de-allocate Buffer memory and  }
  1115.       Destroy_Data_Structures; {    other allocated data structures           }
  1116.    end {if};
  1117. End.
  1118.